package com.yydream.common.aop.controller;

import com.alibaba.fastjson2.JSON;
import com.yydream.common.aop.Secret;
import com.yydream.common.utils.AesUtil;
import com.yydream.common.utils.R;
import com.yydream.common.utils.RsaUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Objects;

@Aspect
@Component
@Slf4j
public class SecretAOPController {

    // 定义切点,使用了@Secret注解的类 或 使用了@Secret注解的方法
    @Pointcut("@within(com.yydream.common.aop.Secret) || @annotation(com.yydream.common.aop.Secret)")
    public void pointcut() {
    }

    // 环绕切面
    @Around("pointcut()")
    public R around(ProceedingJoinPoint point) throws Throwable {
        R result = null;
        // 获取被代理方法参数
        Object[] args = point.getArgs();
        // 获取被代理对象
        Object target = point.getTarget();
        // 获取通知签名
        MethodSignature signature = (MethodSignature) point.getSignature();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String clientPublicKey = request.getHeader("x-sign");
        String xSecret = request.getHeader("x-secret");
        String aesKey = request.getHeader("x-aesKey");
        // 获取被代理方法
        Method pointMethod = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
        // 获取被代理方法上面的注解@Secret
        Secret secret = pointMethod.getAnnotation(Secret.class);
        // 被代理方法上没有，则说明@Secret注解在被代理类上
        if (secret == null) {
            secret = target.getClass().getAnnotation(Secret.class);
        }
        Boolean hasSecret = StringUtils.isNotEmpty(xSecret) && xSecret.equals("1") && secret != null;
        if (hasSecret) {
            // 获取注解上声明的加解密类
            Class clazz = secret.value();
            // 获取注解上声明的加密参数名
            String encryptStrName = secret.encryptStrName();
            if (StringUtils.isNotEmpty(aesKey)) {
                String clientRealAesKey = RsaUtil.decryptByPrivateKey(RsaUtil.getPrivateKey(), aesKey);
                for (int i = 0; i < args.length; i++) {
                    // 如果是clazz类型则说明使用了加密字符串encryptStr传递的加密参数
                    if (clazz.isInstance(args[i])) {
                        //将args[i]转换为clazz表示的类对象
                        Object cast = clazz.cast(args[i]);
                        // 通过反射，执行getEncryptStr()方法，获取加密数据
                        Method method = clazz.getMethod(getMethedName(encryptStrName));
                        // 执行方法，获取加密数据
                        String encryptStr = (String) method.invoke(cast);
                        // 加密字符串是否为空
                        if (StringUtils.isNotBlank(encryptStr)) {
                            // 解密
                            String json = AesUtil.decryptAES(encryptStr, clientRealAesKey);
                            // 转换vo
                            args[i] = JSON.parseObject(json, (Type) args[i].getClass());
                        }
                    }
                    // 其他类型，比如基本数据类型、包装类型就不使用加密解密了
                }
            }
        }

        // 执行请求
        result = (R) point.proceed(args);

        // 判断配置是否需要返回加密
        if (hasSecret && !Objects.isNull(result.getData())) {
            // 获取返回值json字符串
            String jsonString = JSON.toJSONString(result.getData());
            String key = AesUtil.getKey();
            String key2 = RsaUtil.encryptByPublicKey(clientPublicKey, key);
            // 加密
            String s = AesUtil.encryptAES(jsonString, key);
            HttpServletResponse response = requestAttributes.getResponse();
            response.setHeader("x-key", key2);
            result.setData(s);
        }

        return result;
    }

    // 转化方法名
    private String getMethedName(String name) {
        String first = name.substring(0, 1);
        String last = name.substring(1);
        first = StringUtils.upperCase(first);
        return "get" + first + last;
    }

}
